﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Windows.Forms;

namespace Axe.Windows.Forms
{
    public interface ICommand
    {
        bool Do(object document);
        void Undo(object document);
    }

    public enum TransactionEventArgs
    {
        Commit, CommitUndo, CommitRedo, Rollback
    }

    public enum UndoManType
    {
        autoCommit, manualCommit
    }

    public class UndoMan
    {
        public delegate void TransactionEventHandler(object sender, TransactionEventArgs args);

        public UndoMan(int stackCapacity, UndoManType type)
        {
            SetSize(stackCapacity);

            this.type = type;

            if (type == UndoManType.autoCommit)
                Application.Idle += new EventHandler(this.OnIdle);
        }

        public void Undo(object document)
        {
            if (this.stackUndo.Count < 2)
                return;

            //Если транзакция завершена, снимаем контр. точку
            if (this.stackUndo.Peek() == null)
                this.stackUndo.Pop();

            this.stackRedo.Push(null);
            while (this.stackUndo.Peek() != null)
            {
                ICommand cmd = this.stackUndo.Pop() as ICommand;
                this.stackRedo.Push(cmd);
                cmd.Undo(document);
            }
            Transaction(this, TransactionEventArgs.CommitUndo);
        }

        public bool CanUndo()
        {
            return this.stackUndo.Count > 1;
        }

        public void Redo(object document)
        {
            if (this.stackRedo.Count == 0)
                return;

            //Если транзакция не завершена, завершаем её
            if (this.stackUndo.Peek() != null)
                this.stackUndo.Push(null);

            while (this.stackRedo.Peek() != null)
            {
                ICommand cmd = this.stackRedo.Pop() as ICommand;
                this.stackUndo.Push(cmd);
                if (!cmd.Do(document))
                {
                    Rollback(document);
                    return;
                }
            }
            this.stackRedo.Pop();
            this.stackUndo.Push(null);
            Transaction(this, TransactionEventArgs.CommitRedo);
        }

        public bool CanRedo()
        {
            return this.stackRedo.Count > 1;
        }

        public bool Do(object document, ICommand cmd)
        {
            if (!cmd.Do(document))
            {
                Rollback(document);
                return false;
            }
            this.stackUndo.Push(cmd);
            this.stackRedo.Clear();
            return true;
        }

        public void Commit()
        {
            object cmd = this.stackUndo.Peek();
            if (cmd != null)
            {
                this.stackUndo.Push(null);
                Transaction(this, TransactionEventArgs.Commit);
            }
        }

        public void Rollback(object document)
        {
            while (this.stackUndo.Peek() != null)
            {
                ICommand cmd = this.stackUndo.Pop() as ICommand;
                cmd.Undo(document);
            }
            Transaction(this, TransactionEventArgs.Rollback);
        }

        protected void OnIdle(object sender, EventArgs e)
        {
            Commit();
        }

        public void SetSize(int stackCapacity)
        {
            this.stackUndo = new Stack(stackCapacity);
            this.stackUndo.Push(null);
            this.stackRedo = new Stack(stackCapacity);
        }

        public void Reset()
        {
            this.stackUndo.Clear();
            this.stackUndo.Push(null); // Initial Check Point
            this.stackRedo.Clear();
        }

        public event TransactionEventHandler Transaction;
        protected UndoManType type;
        protected Stack stackUndo;
        protected Stack stackRedo;
    }
}
